package org.rainbowinverted.charging.device.connect.bl.server.handler;

import cn.hutool.core.util.HexUtil;
import com.dix.codec.bkv.BKV;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import lombok.extern.slf4j.Slf4j;
import org.rainbowinverted.charging.common.core.StringPadUtils;
import org.rainbowinverted.charging.device.connect.bl.server.message.out.BlDeviceMessageOut;

import java.time.LocalDateTime;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.*;

/**
 * 抽象处理程序
 *
 * @author huaiping
 */
@Slf4j
public abstract class BlAbstractHandler<I, O extends BlDeviceMessageOut> {
    /**
     * 通道处理程序上下文
     */
    private final static Map<Long, ChannelHandlerContext> DEVICE_NO_CHANNEL_MAP =
            new ConcurrentHashMap<>(48);
    /**
     * 通道处理程序上下文
     */
    private final static Map<ChannelHandlerContext, Long> CHANNEL_HASH_CODE_DEVICE_NO_MAP =
            new ConcurrentHashMap<>(48);

    /**
     * promise map <br/>
     * key：指令序列号ID <br/>
     * value：设备反馈信息 <br/>
     * 当服务端主动向设备下发指令时，可向此map中添加一个future <br/>
     * 当设备反馈指令执行结果时，从map中根据指令序列号ID获取future并设置反馈值
     */
    private static final Map<Long, CompletableFuture<?>> PROMISE_MAP = new ConcurrentHashMap<>(48);
    /**
     * put channel
     *
     * @param deviceNo              设备编号
     * @param channelHandlerContext 通道处理程序上下文
     */
    protected final void putChannel(Long deviceNo, ChannelHandlerContext channelHandlerContext) {
        DEVICE_NO_CHANNEL_MAP.putIfAbsent(deviceNo, channelHandlerContext);
        CHANNEL_HASH_CODE_DEVICE_NO_MAP.putIfAbsent(channelHandlerContext, deviceNo);
    }
    /**
     * remove channel
     *
     * @param ctx   设备编号
     */
    protected final void removeChannel(ChannelHandlerContext ctx) {
        Long deviceNo = CHANNEL_HASH_CODE_DEVICE_NO_MAP.remove(ctx);
        DEVICE_NO_CHANNEL_MAP.remove(deviceNo);
    }

    /**
     * 获取channel
     *
     * @param deviceNo 设备编号
     * @return {@link ChannelHandlerContext}
     */
    protected ChannelHandlerContext getChannel(Long deviceNo) {
        return DEVICE_NO_CHANNEL_MAP.get(deviceNo);
    }
    /**
     * 获取deviceNo
     *
     * @param ctx channel
     * @return {@link ChannelHandlerContext}
     */
    protected Long getDeviceNo(ChannelHandlerContext ctx) {
        return CHANNEL_HASH_CODE_DEVICE_NO_MAP.get(ctx);
    }

    public Boolean getOnlineStatus(Long deviceNo) {
        return Objects.nonNull(getChannel(deviceNo)) ? Boolean.TRUE : Boolean.FALSE;
    }

    /**
     * 发送消息
     *
     * @param deviceMessageOut 设备消息OUT
     * @return {@link Boolean}
     */
    public I sendMsg(BlDeviceMessageOut deviceMessageOut) throws Exception {
        ChannelHandlerContext channel = getChannel(deviceMessageOut.getDeviceNo());
        if (Objects.nonNull(channel)) {
            ChannelFuture channelFuture = channel.writeAndFlush(deviceMessageOut);
            try {
                channelFuture.get(10, TimeUnit.SECONDS);
                I promiseValue = getFutureValue(deviceMessageOut.getFrameSerialNo());
                if (Objects.nonNull(promiseValue)) {
                    return promiseValue;
                }
                return null;
            } catch (Exception e) {
                log.error("send message err", e);
                throw e;
            }
        }
        throw new RuntimeException("设备未在线");
    }

    /**
     * 获取promise
     *
     * @param serialNo 序列号
     */
    protected final CompletableFuture<I> getCompleteFuture(Long serialNo) {
        return (CompletableFuture<I>)PROMISE_MAP.get(serialNo);
    }

    /**
     * 完成
     *
     * @param serialNo 序列号
     * @param value    value
     */
    protected final void complete(Long serialNo, I value) {
        CompletableFuture<I> future = getCompleteFuture(serialNo);
        if (Objects.nonNull(future)) {
            future.complete(value);
        }
    }

    /**
     * put promise
     *
     * @param serialNo 序列号
     */
    protected final void createFuture(Long serialNo) {
        CompletableFuture<I> future = new CompletableFuture<>();
        PROMISE_MAP.putIfAbsent(serialNo, future);
    }

    /**
     * 获得promise value
     *
     * @param serialNo 序列号
     * @return {@link Object}
     * @throws ExecutionException   执行异常
     * @throws InterruptedException 中断异常
     */
    public I getFutureValue(Long serialNo) throws ExecutionException, InterruptedException, TimeoutException {
        CompletableFuture<I> future = getCompleteFuture(serialNo);
        if (Objects.nonNull(future)) {
            I i = future.get(10, TimeUnit.SECONDS);
            // remove promise
            PROMISE_MAP.remove(serialNo);
            return i;
        }
        return null;
    }

    /**
     * 获取设备编号
     *
     * @param bkv bkv bkv
     * @return {@link String}
     */
    protected Long getDeviceNo(BKV bkv) {
        return Long.decode(HexUtil.encodeHexStr(bkv.get(0x03).getValue()));
    }

    /**
     * 获取序列号
     *
     * @param bkv bkv
     * @return {@link Long}
     */
    protected Long getSerialNo(BKV bkv) {
        return Long.parseLong(HexUtil.encodeHexStr(bkv.get(0x02).getValue()), 16);
    }

    /**
     * 获取充电枪号
     *
     * @param bkv bkv
     * @return {@link Short}
     */
    protected Integer getGunNo(BKV bkv) {
        // 枪号
        return Integer.parseInt(HexUtil.encodeHexStr(bkv.get(0x08).getValue()), 16);
    }

    /**
     * 获取ack
     *
     * @param bkv bkv
     * @return {@link Byte}
     */
    protected Byte getAck(BKV bkv) {
        return Byte.parseByte(HexUtil.encodeHexStr(bkv.get(0x0f).getValue()), 16);
    }

    /**
     * 获取时间
     *
     * @param bkv bkv
     * @return {@link LocalDateTime}
     */
    protected LocalDateTime getTime(BKV bkv) {
        // 7 byte, 年月日时分秒，年份2 字节
        /*
            年   月  日 时 分 秒
            2023 01 01 08 00 00
         */
        String timeHex = HexUtil.encodeHexStr(bkv.get(0x06).getValue());
        String yearHex = timeHex.substring(0,4);
        String monthHex = timeHex.substring(4,6);
        String dayHex = timeHex.substring(6,8);
        String hourHex = timeHex.substring(8,10);
        String minuteHex = timeHex.substring(10,12);
        String secondHex = timeHex.substring(12,14);
        return LocalDateTime.of(Integer.parseInt(yearHex),
                Integer.parseInt(monthHex),
                Integer.parseInt(dayHex),
                Integer.parseInt(hourHex),
                Integer.parseInt(minuteHex),
                Integer.parseInt(secondHex));
    }

    /**
     * 将时间编码成hex字符串
     * 年月日时分秒，年份·2·字节
     * @param time 时间
     */
    protected void addTime(BKV bkv, LocalDateTime time) {
        String timeHex = time.getYear() +
                String.format("%02d", time.getMonthValue()) + String.format("%02d", time.getDayOfMonth()) +
                String.format("%02d", time.getHour()) + String.format("%02d", time.getMinute()) +
                String.format("%02d", time.getSecond());
        bkv.add(0x06, HexUtil.decodeHex(timeHex));
    }

    /**
     * ack
     *
     * @param bkv bkv
     * @param ack ack value
     */
    protected void addAck(BKV bkv, Object ack) {
        bkv.add(0x0f, HexUtil.decodeHex(
                StringPadUtils.leftPad(ack.toString(), "0", 2)));
    }

    /**
     * 写
     *
     * @param message 消息
     * @param bkv     列表
     */
    public abstract void write(O message, BKV bkv);

    /**
     * 读
     *
     * @param channelHandlerContext 通道处理程序上下文
     * @param bkv                   bkv
     */
    public abstract void read(ChannelHandlerContext channelHandlerContext, BKV bkv);
}
